home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 4 / Apprentice-Release4.iso / Source Code / C++ / Snippets / AppLauncher 1.0d0 / sources / •AZN_AELAUNCH / AZN_TAELaunch.cp next >
Encoding:
Text File  |  1995-10-20  |  31.3 KB  |  1,130 lines  |  [TEXT/CWIE]

  1. /*
  2. *    AZN_TAELaunch.cp
  3. *
  4. *    Static Methods for 'TAELaunch' class
  5. *    A class to locate, launch and manipulate
  6. *    processes & their associated files
  7. *    ( Based on Pascal code by Peter Lewis, [ peter@kagi.com ] )
  8. *    ( Converted to C++ by Gilles Dignard [ gdignard@hookup.net ] )
  9. *    This version by Andrew Nemeth [ aznemeng@zeta.org.au ]
  10. *
  11. *    File Created:        21 Feb 95
  12. *    File Ammended:        21 Feb;
  13. *                    4–9, 12–19, 28, 31 Mar;
  14. *                    3, 4, 10, 18 Apr;
  15. *                    2, 15 May;
  16. *                    25 Jun 95.
  17. */
  18.  
  19. #include    "AZN_TAELaunch.h"                                //    class to remotely launch apps
  20.  
  21. #include    "AZN_DBUG.H"                                    //    debugging utilites
  22.  
  23. #include    "FinderRegistry.h"                                //    finder objects
  24. /*
  25. #include    <AppleEvents.h>
  26. #include    <AEPackObject.h>                                //    the object specifier building utilities
  27. #include    <AERegistry.h>
  28. #include    <AEObjects.h>                                    //    mac OSL constants
  29. #include    <Processes.h>                                    //    mac process manager
  30.  
  31. #include    <Aliases.h>
  32. #include    <Folders.h>
  33. #include    <Gestalt.h>
  34. */
  35.  
  36.  
  37. //    FILE DEFINES…
  38. //
  39. const OSType        kostypeApp         = 'APPL',
  40.                 kostypeCdev         = 'cdev',
  41.                 kostypeCreatorMac     = 'MACS',
  42.                 kostypeFinder         = 'FNDR';
  43.  
  44. enum EMyProcess {    kenumError,
  45.                 kenumLaunchable,
  46.                 kenumRunning
  47.             };
  48.  
  49.  
  50. //    FILE FUNCTIONS…
  51. //
  52. static  Boolean        showHideProcess    ( OSType, Boolean );
  53. static  void            buildVisibilityOSPEC( Str63, AEDesc * );
  54. static  void            addFSSToAEList        ( AEDescList *, short, const FSSpec & );
  55. static  Boolean        docOpenable        ( const FSSpec & );
  56. static  EMyProcess        scanProcesses        ( const FSSpec &, OSType *, ProcessSerialNumber * );
  57. static  void            stallUntilFrontProcess( const ProcessSerialNumber & );
  58.  
  59. static  OSErr            searchPBCatByName    ( ConstStr63Param, short, Boolean, Boolean,
  60.                                           OSType, OSType, FSSpecArrayPtr, short * );
  61. static  OSErr            addAppToDesk        ( FSSpecPtr, OSType );
  62.  
  63. inline  Boolean        bTST( long lgIn, short shBit )
  64.                         {
  65.                         return( (lgIn >> shBit) & 1 );
  66.                         }
  67.  
  68.  
  69.  
  70.  
  71. OSErr    TAELaunch::findApplication( ConstStr63Param     str63AppName,
  72.                                 OSType         ostypeCreator, 
  73.                                 FSSpecPtr     ptrfsspecA )
  74. //
  75. //    Given an application's name & creator OSType,
  76. //    search all mounted volumes (by looking in 
  77. //    desktop database) until a matching app on
  78. //    disc is found.  Then load file's FSSpec into
  79. //    FSSpecPtr provided.  Return OSErr as to
  80. //    success or otherwise.
  81. //
  82. //    This alogrithm searches the desktop!  So if
  83. //    there is something wrong with it, then will have
  84. //    to search desk catalogue (which is much slower).
  85. //
  86. {
  87.     short         i = 0;
  88.     VolumeParam    vparamRec;
  89.     Str63        str63Name = { "\p" };
  90.     DTPBRec         pbdt;
  91.     long         lgBytesFree = 0L;
  92.     OSErr         myErr = noErr;
  93.     Boolean         boolFoundIt = FALSE;
  94.     long         lgFeature = 0L;
  95.     
  96. //                                                        idiot filters
  97.     ASSERT( ptrfsspecA != NULL );
  98.     ASSERT( ostypeCreator > 0L );
  99.     SALT_MEMORY( &vparamRec, sizeof( VolumeParam ) );
  100.     SALT_MEMORY( &pbdt, sizeof( DTPBRec ) );
  101. //                                                        check for system 7
  102.     myErr = ::Gestalt( gestaltSystemVersion, &lgFeature );
  103.     if ( myErr == noErr && ( lgFeature >= 0x0700 ) ) 
  104.         {
  105.         vparamRec.ioCompletion = NULL;
  106.         vparamRec.ioNamePtr = ptrfsspecA->name;
  107. //                                                        cycle thru all mounted vols
  108.         do {
  109.             vparamRec.ioVRefNum = 0;
  110.             vparamRec.ioVolIndex = ++i;
  111.     
  112.             myErr = ::PBGetVInfoSync( (ParmBlkPtr)&vparamRec );
  113. //                                                        grab desktop database
  114.             if ( myErr == noErr ) 
  115.                 {
  116.                 pbdt.ioNamePtr = str63Name;
  117.                 pbdt.ioVRefNum = vparamRec.ioVRefNum;
  118.                 myErr = ::PBDTGetPath( &pbdt );
  119. //                                                        search desktop for app
  120.                 if ( myErr == noErr ) 
  121.                     {
  122.                     pbdt.ioIndex = 0;
  123.                     pbdt.ioFileCreator = ostypeCreator;
  124.                     myErr = ::PBDTGetAPPLSync( &pbdt );
  125.                     if ( myErr == noErr )
  126.                         boolFoundIt = TRUE;
  127.                     }
  128.                 myErr = noErr;
  129.                 }
  130.             } while ( ! boolFoundIt && ( myErr == noErr ) );
  131.         }
  132. //                                                        store data in fsspec
  133.     if ( boolFoundIt ) 
  134.         {
  135.         myErr = noErr;
  136.         ptrfsspecA->vRefNum = pbdt.ioVRefNum;
  137.         ptrfsspecA->parID = pbdt.ioAPPLParID;
  138.         ::BlockMoveData( str63Name, ptrfsspecA->name, str63Name[0]+1 );
  139.         }
  140. //                                                        try to find app by cat. search!
  141.     else if ( str63AppName[0] > 0 )
  142.         {
  143.         myErr = findFile( str63AppName, ostypeCreator, kostypeApp, ptrfsspecA );
  144. //                                                        if found, then insert into desktop
  145.         if ( myErr == noErr )
  146.             myErr = addAppToDesk( ptrfsspecA, ostypeCreator );
  147.         }
  148.  
  149.     return( myErr );
  150. }
  151.  
  152.  
  153.  
  154. OSErr    TAELaunch::findFile( ConstStr63Param    str63Name,
  155.                         OSType             ostypeCreator, 
  156.                         OSType            ostypeFileType, 
  157.                         FSSpecPtr         ptrfsspecFile )
  158. //
  159. //    Scan all volumes to find a file
  160. //    called 'str63Name' with a given creator & type,
  161. //    then load hit into FSSpecPtr provided.
  162. //    Return OSErr as to success
  163. //
  164. {
  165.     short         i = 0, 
  166.                 shtHits = 1;
  167.     VolumeParam    vparamRec;
  168.     Str63        str63VolName = { "\p" };
  169.     Boolean        boolFoundIt = FALSE;
  170.     OSErr        myErr = noErr,
  171.                 volErr = noErr;
  172. //                                                        idiot filters
  173.     ASSERT( ostypeCreator > 0L );
  174.     ASSERT( ostypeFileType > 0L );
  175.     ASSERT( ptrfsspecFile != NULL );
  176.  
  177.     vparamRec.ioCompletion = NULL;
  178.     vparamRec.ioNamePtr = str63VolName;
  179. //                                                        search a vol
  180.     do {
  181.         vparamRec.ioVRefNum = 0;
  182.         vparamRec.ioVolIndex = ++i;
  183.  
  184.         volErr = ::PBGetVInfoSync( (ParmBlkPtr)&vparamRec );
  185.         if ( volErr == noErr )
  186.             {
  187.             myErr = searchPBCatByName( str63Name,
  188.                                 vparamRec.ioVRefNum,
  189.                                 TRUE,                    /* Search for file */
  190.                                 FALSE,                    /* Exact name match? */
  191.                                 ostypeCreator,
  192.                                 ostypeFileType,
  193.                                 ptrfsspecFile,
  194.                                 &shtHits );
  195. //                                                        set for next sweep
  196.             shtHits = 1;
  197.             boolFoundIt = ( myErr == noErr );
  198.             }
  199.         } while ( ! boolFoundIt && ( volErr == noErr ) );
  200. //                                                        equate result to success    
  201.     myErr = boolFoundIt ? noErr : fnfErr;
  202.     
  203.     return( myErr );
  204. }
  205.  
  206.  
  207.  
  208. Boolean    TAELaunch::openItemInFinder( const FSSpec & fsspecITEM, 
  209.                                 Boolean         boolToFront )
  210. //
  211. //    Send in an FSSpec of an item you
  212. //    wish the finder to launch.  Set
  213. //    'boolToFront' to bring it to the front(!)
  214. //    Returns TRUE if everything went okay
  215. //
  216. {
  217.     AEDesc                aedescTarget         = { typeNull, NULL };
  218.     AEDescList            aedesclistFiles     = { typeNull, NULL };
  219.     AppleEvent            aeEvent             = { typeNull, NULL },
  220.                         aeReply             = { typeNull, NULL };
  221.     AESendMode            aesendMode;
  222.     long                    lgFeature         = 0L;
  223.     FSSpec                fsspecFinder;
  224.     OSType                ostypeSig         = 0L;
  225.     ProcessSerialNumber        psnOriginal,
  226.                         psnProcess, 
  227.                         psnFndr;
  228.     OSErr                myErr             = noErr;
  229.     
  230. //                                                        use Gestalt to check for OSL compliance
  231.     myErr = ::Gestalt( gestaltFinderAttr, &lgFeature );
  232.     if ( ( myErr == noErr ) && bTST( lgFeature, gestaltOSLCompliantFinder ) )
  233.         NULL;
  234.     else
  235.         return( FALSE );
  236. //                                                        check existing processes first…
  237.     switch( scanProcesses( fsspecITEM, &ostypeSig, &psnProcess ) )
  238.         {
  239.         case kenumError:
  240.             return( FALSE );
  241.             break;
  242. //                                                        fsspecITEM is openable
  243.         case kenumLaunchable:
  244.             myErr = ::GetFrontProcess( &psnOriginal );
  245.             if ( myErr != noErr )
  246.                 return( FALSE );
  247.             break;
  248. //                                                        item is already open & running
  249.         case kenumRunning:
  250.             if ( boolToFront )
  251.                 ::SetFrontProcess( &psnProcess );
  252.             return( TRUE );
  253.             break;
  254.         }
  255. //                                                        grab process for finder
  256.     if ( findProcess( kostypeCreatorMac, kostypeFinder, &psnFndr, &fsspecFinder ) )
  257.         {
  258. //                                                        prepare 'odoc' AE for finder
  259.         myErr = ::AECreateDesc( typeProcessSerialNumber,
  260.                             &psnFndr, sizeof( ProcessSerialNumber ),
  261.                             &aedescTarget );
  262.         if (myErr == noErr)
  263.             myErr = ::AECreateAppleEvent(    kCoreEventClass,
  264.                                     kAEOpenDocuments,
  265.                                     &aedescTarget,
  266.                                     kAutoGenerateReturnID,
  267.                                     kAnyTransactionID,
  268.                                     &aeEvent );
  269. //                                                        prepare FSSpec's to send to finder
  270.         if ( myErr == noErr )
  271.             myErr = ::AECreateList( nil, 0, FALSE, &aedesclistFiles );
  272.         
  273.         if ( myErr == noErr )
  274.             addFSSToAEList( &aedesclistFiles, 1, fsspecITEM );
  275.         
  276.         if ( myErr == noErr )
  277.             myErr = ::AEPutParamDesc( &aeEvent, keyDirectObject, &aedesclistFiles );
  278. //                                                        send AE to finder
  279.         if (myErr == noErr) 
  280.             {
  281. //                                                        set AE switches
  282.             if ( boolToFront )
  283.                 aesendMode = kAENoReply + kAECanSwitchLayer + kAEAlwaysInteract;
  284.             else
  285.                 aesendMode = kAEWaitReply + kAECanSwitchLayer + kAENeverInteract;
  286.                 //    N.B  Use 'kAEWaitReply' instead of 'kAENoReply' to allow
  287.                 //    OS time to cope with AppleEvent!
  288.  
  289.             myErr = ::AESend(    &aeEvent,
  290.                             &aeReply,
  291.                             aesendMode,
  292.                             kAEHighPriority,
  293.                             kAEDefaultTimeout,
  294.                             nil, nil);
  295.             ASSERT( myErr == noErr );
  296.             }
  297. //                                                        tidy up
  298.         ::AEDisposeDesc( &aeEvent );
  299.         ::AEDisposeDesc( &aeReply );
  300.         ::AEDisposeDesc( &aedesclistFiles );
  301.         ::AEDisposeDesc( &aedescTarget );            
  302.  
  303.         //    Finder always puts new processes in foreground,
  304.         //    so only need to pull tricks if want to put
  305.         //    it into the BACKGROUND
  306.         //
  307.         if ( myErr == noErr && ! boolToFront )
  308.             {
  309. //                                                        find PSN of process just launched
  310.             if ( kenumError != scanProcesses( fsspecITEM, &ostypeSig, &psnProcess ) )
  311.                 {
  312. //                                                        stall until launched app is indeed in front!
  313.                 stallUntilFrontProcess( psnProcess );
  314. //                                                        NOW make current app front!            
  315.                 makeCurrentProcessFront();
  316.                 }
  317.             }
  318.         }
  319.  
  320.     return( myErr == noErr );
  321. }
  322.  
  323.  
  324.  
  325. Boolean    TAELaunch::openDocInApp( OSType             ostypeSig, 
  326.                             const FSSpec &    fsspecDoc, 
  327.                             Boolean            boolToFront )
  328. //
  329. //    ASSUMES THAT APP IS ALREADY RUNNING!
  330. //
  331. //    Send in the OSType of an app. and
  332. //    the FSSpec of a document you wish to
  333. //    open in it, return TRUE if success
  334. //
  335. {
  336.     const long            klgHeapNeeded = 4096L;
  337.  
  338.     AEDesc                 aedescTarget         = { typeNull, NULL };
  339.     AppleEvent             aeEvent             = { typeNull, NULL },
  340.                         aeReply             = { typeNull, NULL };
  341.     AEDescList             aedesclistFiles     = { typeNull, NULL };
  342.     AESendMode             aesendMode;
  343.     ProcessSerialNumber     psnProcess;
  344.     FSSpec                 fsspecApp;
  345.     long                 lgFeature = 0L,
  346.                         lgTotHeapSize = 0L,
  347.                         lgContigHeapSize = 0L;
  348.     OSErr                 myErr = noErr;
  349.  
  350. //                                                        idiot filter
  351.     ASSERT( ostypeSig > 0L );
  352. //                                                        make sure we've got enough heap to play with!
  353.     ::PurgeSpace( &lgTotHeapSize, &lgContigHeapSize );
  354.     if ( lgContigHeapSize <= klgHeapNeeded )
  355.         return( FALSE );
  356. //                                                        check gestalt to see if we can launch
  357.     myErr = ::Gestalt( gestaltOSAttr, &lgFeature );
  358.     if ( ( myErr == noErr ) && bTST( lgFeature, gestaltLaunchControl ) )
  359.         NULL;
  360.     else
  361.         return( FALSE );
  362. //                                                        find app. process and launch with doc
  363.     if ( findProcess( ostypeSig, kostypeApp, &psnProcess, &fsspecApp ) ) 
  364.         {
  365. //                                                        check to see if item isn't already open
  366.         if ( docOpenable( fsspecDoc ) )
  367.             {
  368. //                                                        create AE for target application
  369.             myErr = ::AECreateDesc( typeProcessSerialNumber, &psnProcess, 
  370.                                 sizeof(psnProcess), &aedescTarget );
  371.     
  372.             myErr = ::AECreateAppleEvent(    kCoreEventClass,
  373.                                     kAEOpenDocuments,
  374.                                     &aedescTarget,
  375.                                     kAutoGenerateReturnID,
  376.                                     kAnyTransactionID,
  377.                                     &aeEvent );
  378. //                                                        prepare AEDesc(s) for doc to be launched
  379.             myErr = ::AECreateList( nil, 0, false, &aedesclistFiles );
  380.             addFSSToAEList( &aedesclistFiles, 1, fsspecDoc );
  381.             myErr = ::AEPutParamDesc( &aeEvent, keyDirectObject, &aedesclistFiles );
  382. //                                                        set AE switches
  383.             if ( boolToFront )
  384.                 aesendMode = kAENoReply + kAECanSwitchLayer + kAEAlwaysInteract;
  385.             else
  386.                 aesendMode = kAEWaitReply + kAECanSwitchLayer + kAENeverInteract;
  387.                 //    N.B  Use 'kAEWaitReply' instead of 'kAENoReply' to allow
  388.                 //    OS time to cope with AppleEvent!
  389. //                                                        send 'odoc' AE to application
  390.             myErr = ::AESend(    &aeEvent,
  391.                             &aeReply,
  392.                             aesendMode,
  393.                             kAEHighPriority,
  394.                             kNoTimeOut,
  395.                             nil, nil);
  396.             ASSERT( myErr == noErr );
  397. //                                                        tidy up
  398.             myErr = ::AEDisposeDesc( &aedescTarget );
  399.             myErr = ::AEDisposeDesc( &aeEvent );
  400.             myErr = ::AEDisposeDesc( &aeReply );
  401.             myErr = ::AEDisposeDesc( &aedesclistFiles );
  402.             }
  403. //                                                        if required, bring app to front
  404.         if ( boolToFront )
  405.             myErr = ::SetFrontProcess( &psnProcess );
  406.         }
  407. //                                                        application is not running!
  408.     else
  409.         myErr = afpItemNotFound;
  410.  
  411.     return( myErr == noErr );
  412. }
  413.  
  414.  
  415.  
  416. Boolean    TAELaunch::findProcess( OSType                 ostypeCreator, 
  417.                             OSType                 ostypeProcType, 
  418.                             ProcessSerialNumber *    ptrPSN, 
  419.                             FSSpecPtr                ptrfsspecApp )
  420. //
  421. //    Step thru all active processes until
  422. //    one is found which matches the creator & 
  423. //    file type OSType's given.  Load PSN &
  424. //    FSSpec of process into ptr's provided.
  425. //    Return TRUE if success.
  426. //
  427. {
  428.     ProcessInfoRec     procirRec;
  429.     long             lgFeature = 0L;
  430.     Boolean             boolFoundIt = FALSE;
  431.     OSErr            myErr = noErr;
  432.  
  433. //                                                        idiot filters
  434.     ASSERT( ostypeCreator > 0L );
  435.     ASSERT( ostypeProcType > 0L );
  436.     ASSERT( ptrPSN != NULL );
  437.     ASSERT( ptrfsspecApp != NULL );
  438.     SALT_MEMORY( &procirRec, sizeof( ProcessInfoRec ) );
  439. //                                                        check gestalt to see if we can launch
  440.     myErr = ::Gestalt( gestaltOSAttr, &lgFeature );
  441.     if ( ( myErr == noErr ) && bTST( lgFeature, gestaltLaunchControl ) )
  442.         NULL;
  443.     else
  444.         return( FALSE );
  445.     
  446.     ptrPSN->highLongOfPSN = 0;
  447.     ptrPSN->lowLongOfPSN = kNoProcess;
  448.     procirRec.processInfoLength = sizeof( ProcessInfoRec );
  449.     procirRec.processName = nil;
  450.     procirRec.processAppSpec = ptrfsspecApp;
  451. //                                                        step thru all active processes until match
  452.     while ( ::GetNextProcess( ptrPSN ) == noErr ) 
  453.         {
  454.         if ( ::GetProcessInformation( ptrPSN, &procirRec ) == noErr ) 
  455.             {
  456.             if ( ( procirRec.processType == long( ostypeProcType ) ) &&
  457.                  ( procirRec.processSignature == ostypeCreator ) ) 
  458.                  {
  459.                 boolFoundIt = TRUE;
  460.                 break;
  461.                 }
  462.             }
  463.         }
  464.     
  465.     return( boolFoundIt );
  466. }
  467.  
  468.  
  469.  
  470. Boolean    TAELaunch::isFrontProcess( OSType ostypeSig )
  471. //
  472. //    Walk process queue to see if required
  473. //    process is running in front
  474. //
  475. {
  476.     ProcessSerialNumber     psnTarget,
  477.                         psnFront;
  478.     FSSpec                 fsspecApp;
  479.     Boolean                boolResult = FALSE;
  480.     OSErr                 myErr = noErr;
  481.     
  482. //                                                        idiot filter
  483.     ASSERT( ostypeSig > 0L );
  484. //                                                        find app
  485.     if ( findProcess( ostypeSig, kostypeApp, &psnTarget, &fsspecApp ) ) 
  486.         {
  487.         myErr = ::GetFrontProcess( &psnFront );
  488.         if ( myErr == noErr )
  489.             ::SameProcess( &psnFront, &psnTarget, &boolResult );
  490.         }
  491.  
  492.     return( boolResult );
  493. }
  494.  
  495.  
  496.  
  497. void        TAELaunch::makeCurrentProcessFront( void )
  498. //
  499. //    Use process manager tricks to
  500. //    force current process to the front
  501. //
  502. {
  503.     ProcessSerialNumber        psnCurrent;
  504.  
  505.  
  506.     ::GetCurrentProcess( &psnCurrent );
  507.  
  508.     ::SetFrontProcess( &psnCurrent );
  509.  
  510.     stallUntilFrontProcess( psnCurrent );
  511. }
  512.  
  513.  
  514.  
  515. void        TAELaunch::hideProcess( OSType ostypeSig )
  516. //
  517. //    Call showHideProcess() such that
  518. //    required process is hidden
  519. //
  520. {
  521.     Boolean    boolResult = FALSE;
  522.  
  523.     boolResult = showHideProcess( ostypeSig, FALSE );
  524. //    ASSERT( boolResult );
  525.     //
  526.     //    No need to die if fail!
  527. }
  528.  
  529.  
  530.  
  531. void        TAELaunch::showProcess( OSType ostypeSig )
  532. //
  533. //    Call showHideProcess() such that
  534. //    required process is shown
  535. //    N.B.  will NOT bring process into
  536. //    foreground, it will only show it!  
  537. //    Use 'TAELaunch::openItemInFinder( FSSpec, TRUE );
  538. //    if you want foreground
  539. //
  540. {
  541.     Boolean    boolResult = FALSE;
  542.  
  543.     boolResult = showHideProcess( ostypeSig, TRUE );
  544. //    ASSERT( boolResult );
  545.     //
  546.     //    No need to die if fail!
  547. }
  548.  
  549.  
  550.  
  551. void        TAELaunch::killProcess( OSType ostypeSig )
  552. //
  553. //    Send quit AE to a running application
  554. //
  555. {
  556.     const long            klgTimeOut = 5L * 60L;
  557.  
  558.     AEAddressDesc             aedescTarget         = { typeNull, NULL };
  559.     AppleEvent             aeEvent             = { typeNull, NULL },
  560.                          aeReply             = { typeNull, NULL };
  561.     ProcessSerialNumber     psnProcess;
  562.     FSSpec                 fsspecApp;
  563.     OSErr                 myErr = noErr;
  564.     
  565. //                                                        idiot filter
  566.     ASSERT( ostypeSig > 0L );
  567. //                                                        find app and shut it down
  568.     if ( findProcess( ostypeSig, kostypeApp, &psnProcess, &fsspecApp ) ) 
  569.         {
  570.         myErr = ::AECreateDesc(    typeProcessSerialNumber,
  571.                                 &psnProcess,
  572.                                 sizeof( psnProcess ),
  573.                                 &aedescTarget );
  574.         myErr = ::AECreateAppleEvent(    kCoreEventClass,
  575.                                 kAEQuitApplication,
  576.                                 &aedescTarget,
  577.                                 kAutoGenerateReturnID,
  578.                                 kAnyTransactionID,
  579.                                 &aeEvent );
  580. //                                                        send 'quit' AE
  581.         myErr = ::AESend(    &aeEvent,
  582.                         &aeReply,
  583.                         kAENoReply,
  584.                         kAEHighPriority,
  585.                         klgTimeOut,
  586.                         nil, nil );
  587.         ASSERT( myErr == noErr );
  588. //                                                        tidy up
  589.         myErr = ::AEDisposeDesc(&aedescTarget);
  590.         myErr = ::AEDisposeDesc( &aeEvent );
  591.         myErr = ::AEDisposeDesc( &aeReply );
  592.         }
  593. }
  594.  
  595.  
  596.  
  597. Boolean    TAELaunch::openControlPanel( OSType ostypeCreator )
  598. //
  599. //    Given the OSType creator of a control
  600. //    panel, fire it up into foreground and 
  601. //    return TRUE if everything went okay
  602. //
  603. {
  604.     ProcessSerialNumber        psnFndr;
  605.     FSSpec                 fsspecCPanel,
  606.                         fsspecFinder;
  607.     HFileParam            hfpRec;
  608.     short                i = 0;
  609.     Boolean                boolResult = FALSE;
  610.     OSErr                myErr = noErr;
  611.  
  612. //                                                        idiot filters
  613.     ASSERT( ostypeCreator > 0L );
  614.     SALT_MEMORY( &hfpRec, sizeof( HFileParam ) );
  615. //                                                        locate 'Control Panels' folder
  616.     myErr = ::FindFolder( kOnSystemDisk, kControlPanelFolderType, FALSE,
  617.                         &(fsspecCPanel.vRefNum), 
  618.                         &(fsspecCPanel.parID) );
  619.     if ( myErr == noErr ) 
  620.         {
  621.         hfpRec.ioNamePtr = fsspecCPanel.name;
  622.         hfpRec.ioVRefNum = fsspecCPanel.vRefNum;
  623. //                                                        cycle thru all files in folder until match
  624.         do {
  625.             hfpRec.ioDirID = fsspecCPanel.parID;
  626.             hfpRec.ioFDirIndex = ++i;
  627.  
  628.             myErr = ::PBHGetFInfoSync( (HParmBlkPtr)&hfpRec );
  629.             if ( myErr == noErr ) 
  630.                 {
  631.                 if ( ( hfpRec.ioFlFndrInfo.fdType == kostypeCdev ) &&
  632.                     ( hfpRec.ioFlFndrInfo.fdCreator == ostypeCreator ) )
  633.                     //    the fsspec provided by user is already
  634.                     //    filled with the elements of the control panel,
  635.                     //    thus there's nothing more to do!
  636.                     break;
  637.                 }
  638.             } while ( myErr == noErr );
  639.         }
  640. //                                                        now open control panel!
  641.     if ( myErr == noErr )
  642.         {
  643.         if ( openItemInFinder( fsspecCPanel, TRUE ) )
  644.             {
  645.             //    need to force the finder to the foreground
  646.             //    to put the Control Panel in front
  647.             //
  648.             if ( findProcess( kostypeCreatorMac, kostypeFinder, &psnFndr, &fsspecFinder ) )
  649.                 {
  650. //                                                        make sure Finder is in front!
  651.                 myErr = ::SetFrontProcess( &psnFndr );
  652.                 boolResult = ( noErr == myErr );
  653.                 }
  654.             }
  655.         }
  656.  
  657.     return( boolResult );
  658. }
  659.  
  660.  
  661.  
  662. //    LOCAL FUNCTIONS…
  663. //
  664.  
  665.  
  666.  
  667. Boolean        docOpenable( const FSSpec & fsspecFile )
  668. //
  669. //    Check to see if a document is not already open.
  670. //    Do this by trying to open data fork of file 
  671. //    with write permission.  
  672. //    Return TRUE if attempt is successful.
  673. //
  674. {
  675.     short        shRefNum = 0;
  676.     OSErr        myErr = noErr;
  677.     Boolean        boolDatOpenable = FALSE;
  678. //                                                        now try to open the data fork
  679.     myErr = ::FSpOpenDF( (FSSpecPtr)&fsspecFile, 
  680.                     fsWrPerm, 
  681.                     &shRefNum );
  682. //                                                        can open file, close data fork
  683.     if ( myErr == noErr )
  684.         {
  685.         ::FSClose( shRefNum );
  686.         boolDatOpenable = TRUE;
  687.         }
  688. //                                                        check if user supplied dumb FSSpec
  689. //                                                        let calling function handle it!
  690.     else if ( myErr == fnfErr )
  691.         return( FALSE );
  692.  
  693.     return( boolDatOpenable );
  694. }
  695.  
  696.  
  697.  
  698. EMyProcess    scanProcesses( const FSSpec         & fsspecITEM,
  699.                         OSType            *ptrostypeSig,
  700.                         ProcessSerialNumber    *ptrPSN )
  701. //
  702. //    Given the FSSpec of an item about to be launched, check
  703. //    current processes to see if is/ needs to be launched.
  704. //    If a process of the same kind is found,
  705. //    then dumps its process signature & PSN
  706. //    into ptrs provided
  707. //
  708. //    Returns (EMyProcess) :
  709. //    kenumError        something bad happened…
  710. //    kenumLaunchable    no equivalent process running
  711. //                    OR…
  712. //                    same creator running, but fsspec provided
  713. //                    is not open
  714. //    kenumRunning        creator & filetype of FSSpec provided
  715. //                    is identical to a process already running
  716. //                    OR…
  717. //                    FSSpec provided is open and its 'owner'
  718. //                    application is running
  719. //                    (hence no need to do anything)
  720. //
  721. {
  722.     FInfo            finfoRec;
  723.     ProcessInfoRec     procirRec;
  724.     FSSpec            fsspecProc;
  725.     long             lgFeature = 0L;
  726.     OSErr            myErr = noErr;
  727.     EMyProcess        eResult = kenumLaunchable;
  728.  
  729. //                                                        idiot filters
  730.     ASSERT( ptrostypeSig != NULL );
  731.     ASSERT( ptrPSN != NULL );
  732.     SALT_MEMORY( &finfoRec, sizeof( FInfo ) );
  733.     SALT_MEMORY( &procirRec, sizeof( ProcessInfoRec ) );
  734. //                                                        check gestalt to see if we can launch
  735.     myErr = ::Gestalt( gestaltOSAttr, &lgFeature );
  736.     if ( ( myErr == noErr ) && bTST( lgFeature, gestaltLaunchControl ) )
  737.         NULL;
  738.     else
  739.         return( kenumError );
  740. //                                                        get OSTypes of fsspecITEM item
  741.     myErr = ::FSpGetFInfo( (FSSpecPtr)&fsspecITEM, &finfoRec );
  742.     if ( myErr != noErr )
  743.         return( kenumError );
  744. //                                                        prepare to examine running processes
  745.     ptrPSN->highLongOfPSN = 0;
  746.     ptrPSN->lowLongOfPSN = kNoProcess;
  747.     procirRec.processInfoLength = sizeof( ProcessInfoRec );
  748.     procirRec.processName = nil;
  749.     procirRec.processAppSpec = &fsspecProc;
  750. //                                                        step thru all active processes until match
  751.     while ( ::GetNextProcess( ptrPSN ) == noErr ) 
  752.         {
  753.         if ( ::GetProcessInformation( ptrPSN, &procirRec ) == noErr ) 
  754.             {
  755.              if ( procirRec.processSignature == finfoRec.fdCreator )
  756.                  {
  757.                 if ( procirRec.processType == long( finfoRec.fdType ) )
  758.                     eResult = kenumRunning;
  759.  
  760.                 else if ( ! docOpenable( fsspecITEM ) )
  761.                     eResult = kenumRunning;
  762.                 //    Some notes on this.  If a process of the
  763.                 //    same creator is running and the FSSpec
  764.                 //    provided by the user is already open,
  765.                 //    then I assume that the FSSpec is open
  766.                 //    in the running application!
  767. //                                                        assign data of matching process
  768.                 *ptrostypeSig = procirRec.processSignature;
  769.                 //    ptrPSN has already been filled!
  770.                 break;
  771.                 }
  772.             }
  773.         }
  774.     
  775.     return( eResult );
  776. }
  777.  
  778.  
  779.  
  780. void        stallUntilFrontProcess( const ProcessSerialNumber & psnProcess )
  781. //
  782. //    Call 'WaitNextEvent()' until required process
  783. //    is in front.  Will automatically cut out
  784. //    after a maximum of 'kshLimit' (5) calls
  785. //    (if it hasn't come to front in that time
  786. //    then it never will!)
  787. //
  788. {
  789.     const short            kshLimit = 5;
  790.     const long            klgGNEInterval = GetCaretTime();
  791.  
  792.     ProcessSerialNumber        psnFront;
  793.     EventRecord            evRec;
  794.     short                i = 0;
  795.     Boolean                boolInFront = FALSE;
  796.  
  797.  
  798.     while ( FALSE == boolInFront && i++ < kshLimit )
  799.         {
  800.         ::WaitNextEvent( everyEvent, &evRec, klgGNEInterval, NULL );
  801.         ::GetFrontProcess( &psnFront );
  802.         ::SameProcess( &psnFront, &psnProcess, &boolInFront );
  803.         }        
  804. }
  805.  
  806.  
  807.  
  808. void        addFSSToAEList( AEDescList     *ptrDescList, 
  809.                     short         shRow, 
  810.                     const FSSpec     & fsspecFile )
  811. //
  812. //    Given a FSSpec, add it to a AEDescList at
  813. //    row 'shRow'
  814. //
  815. {
  816.     AliasHandle    haliasFile;
  817.     OSErr        myErr = noErr;
  818.     
  819. //                                                        idiot filters
  820.     ASSERT( ptrDescList != NULL );
  821.     ASSERT( shRow >= 0 );
  822. //                                                        create alias handle & insert into AEDescList
  823.     myErr = ::NewAlias( nil, &fsspecFile, &haliasFile );
  824.     if (myErr == noErr) 
  825.         {
  826.         ::HLock((Handle) haliasFile);
  827.         myErr = ::AEPutPtr( ptrDescList, shRow, typeAlias,
  828.                         (Ptr)*haliasFile, (*haliasFile)->aliasSize );
  829.         ::DisposeHandle( (Handle) haliasFile );
  830.         }
  831. }
  832.  
  833.  
  834.  
  835. Boolean    showHideProcess( OSType ostypeSig, Boolean boolShow )
  836. //
  837. //    Send a boolean kAESetData AE to the finder to
  838. //    show or hide a running process of signature 'ostypeSig'
  839. //
  840. {
  841.     AEDesc                 aedescOSPEC        = { typeNull, NULL };
  842.     AEAddressDesc            aedescFinderAddr     = { typeNull, NULL };
  843.     AppleEvent            aeEvent             = { typeNull, NULL },
  844.                         aeReply             = { typeNull, NULL };
  845.     ProcessSerialNumber        psnProc;
  846.     FSSpec                fsspecProc;
  847.     long                    lgFeature            = 0L;
  848.     OSErr                myErr             = noErr;
  849.  
  850. //                                                        idiot filter
  851.     ASSERT( ostypeSig > 0L );
  852. //                                                        use Gestalt to check for OSL compliance
  853.     myErr = ::Gestalt( gestaltFinderAttr, &lgFeature );
  854.     if ( ( myErr == noErr ) && bTST( lgFeature, gestaltOSLCompliantFinder ) )
  855.         NULL;
  856.     else
  857.         return( FALSE );
  858. //                                                        find name of process
  859.     if ( ! TAELaunch::findProcess( ostypeSig, kostypeApp, &psnProc, &fsspecProc ) )
  860.         return( FALSE );
  861. //                                                        create object specifier
  862.     buildVisibilityOSPEC( fsspecProc.name, &aedescOSPEC );
  863. //                                                        get PSN of finder
  864.     TAELaunch::findProcess( kostypeCreatorMac, kostypeFinder, &psnProc, &fsspecProc );
  865. //                                                        create AE targeted at finder
  866.     myErr = ::AECreateDesc( typeProcessSerialNumber, &psnProc,
  867.                         sizeof( ProcessSerialNumber ), &aedescFinderAddr );
  868.         
  869.     myErr = ::AECreateAppleEvent( kAECoreSuite, kAESetData,
  870.                             &aedescFinderAddr, kAutoGenerateReturnID,
  871.                             kAnyTransactionID, &aeEvent );
  872.  
  873.     ASSERT( myErr == noErr );    
  874. //                                                        place OSPEC in AE    
  875.     myErr = ::AEPutParamDesc( &aeEvent, keyDirectObject, &aedescOSPEC );
  876.  
  877.     ASSERT( myErr == noErr );    
  878. //                                                        place boolean in AE
  879.     myErr = ::AEPutParamPtr( &aeEvent, keyAEData, typeBoolean,
  880.                         (Ptr)&boolShow, sizeof( Boolean ) );
  881.  
  882.     ASSERT( myErr == noErr );    
  883. //                                                        send AE on its merry way
  884.     myErr = ::AESend( &aeEvent, &aeReply, 
  885.                     kAEWaitReply + kAENeverInteract,
  886.                     kAEHighPriority, 
  887.                     kAEDefaultTimeout, 
  888.                     nil, nil );
  889.             //    N.B  Use 'kAEWaitReply' instead of 'kAENoReply' to allow
  890.             //    OS time to cope with AppleEvent!
  891.  
  892.     ASSERT( myErr == noErr );    
  893. //                                                        tidy up
  894.     ::AEDisposeDesc( &aedescOSPEC );
  895.     ::AEDisposeDesc( &aedescFinderAddr );
  896.     ::AEDisposeDesc( &aeEvent );
  897.     ::AEDisposeDesc( &aeReply );
  898.  
  899.     return( TRUE );    
  900. }
  901.  
  902.  
  903.  
  904. void        buildVisibilityOSPEC( Str63     str63Name, 
  905.                         AEDesc     *ptraedescOSPEC )
  906. //
  907. //    Build an object specifier for the visibility
  908. //    property of a process called 'str63Name'
  909. //    running in the finder (phew!)
  910. //
  911. {
  912.     const Boolean    kboolDisposeInputs = TRUE;
  913.  
  914.     AEDesc             nullContainer         = { typeNull, NULL },
  915.                     ospecProc         = { typeNull, NULL },
  916.                     aedescName        = { typeNull, NULL },
  917.                     aedescVisi         = { typeNull, NULL };
  918.     long                lgVisiProperty     = pVisible;
  919.     OSErr            myErr            = noErr;
  920.  
  921. //                                                        idiot filters
  922.     ASSERT( str63Name[0] > 0 );
  923.     ASSERT( ptraedescOSPEC != NULL );
  924. //                                                        create AEDesc for process name
  925.     myErr = ::AECreateDesc( typeChar, &str63Name[1], 
  926.                         str63Name[0], &aedescName );
  927.  
  928.     ASSERT( myErr == noErr );
  929.  
  930. //    create an ospec to indicate that we are playing with
  931. //    a process called 'str63Name' of class 'cProcess'
  932. //    running in the finder
  933.  
  934.     myErr = ::CreateObjSpecifier( cProcess, &nullContainer, 
  935.                             formName, &aedescName, 
  936.                             kboolDisposeInputs, &ospecProc );
  937.     
  938.     ASSERT( myErr == noErr );
  939. //                                                        create AEDesc for visibility property
  940.     myErr = ::AECreateDesc( typeType, &lgVisiProperty, 
  941.                         sizeof( long ), &aedescVisi );
  942.     
  943.     ASSERT( myErr == noErr );
  944.  
  945. //    now build a specifier representing the 'pVisible' property
  946. //    of the target process, using 'ospecProc' as its container...
  947.  
  948.     myErr = ::CreateObjSpecifier( cProperty, &ospecProc,
  949.                         formPropertyID, &aedescVisi, 
  950.                         kboolDisposeInputs, ptraedescOSPEC );
  951.     
  952.     ASSERT( myErr == noErr );
  953. }
  954.  
  955.  
  956.  
  957. OSErr        searchPBCatByName( ConstStr63Param     str63Name,
  958.                             short            shVRefNum, 
  959.                             Boolean            boolForFile,
  960.                             Boolean            boolExactName,
  961.                             OSType            ostypeFileCreator,
  962.                             OSType            ostypeFileType,
  963.                             FSSpecArrayPtr        ptrfsspecArray,
  964.                             short            *ptrshHits )
  965. //
  966. //    Search a volume using PBCatSearch() to 
  967. //    turn up an array of FSSpecs (if neccessary)
  968. //    *ptrshHits on input sets limit of matches, on output shows
  969. //    actual number of 'hits' found.
  970. //
  971. {
  972. //                                                        use a 16K search cache for speed
  973.     const long        klgBuffSize = 16384L;
  974. //                                                        number to find in ONE SWEEP
  975.     const short        kshSweepMatches = 20;
  976. //                                                        parameter block for PBCatSearch
  977.     CSParam            csparamRec;
  978. //                                                        search criteria, part 1 & 2
  979.     CInfoPBRec        cipbrRec_1;
  980.     CInfoPBRec        cipbrRec_2;
  981.     Str63            str63FindName = { "\p" };
  982. //                                                        each sweep's results storage
  983.     FSSpec            fsspecHitsArray[ kshSweepMatches ];
  984.     OSErr            myErr = noErr;
  985.     Boolean            boolDone = FALSE, boolTooMany = FALSE;
  986.     short            i, shNumFound = 0;
  987.     Ptr                ptrSearchBuff = nil;
  988.  
  989. //                                                        idiot filters
  990.     ASSERT( str63Name[0] > 0 );
  991.     ASSERT( ptrfsspecArray != NULL );
  992.     ASSERT( ptrshHits != NULL && *ptrshHits > 0 );
  993. //                                                        set up search buffer memory
  994.     ptrSearchBuff = NewPtr( klgBuffSize * sizeof( unsigned char ) );
  995.     if ( ptrSearchBuff == nil )    
  996.         return( memFullErr );
  997. //                                                        make local copy of const string
  998.     BlockMoveData( (Str63*)str63Name, str63FindName, str63Name[0]+1 );
  999.  
  1000.     //    GENERAL SEARCH SETTINGS…
  1001.     //
  1002.     csparamRec.ioCompletion = nil;                        //    no completion routine
  1003.     csparamRec.ioNamePtr = nil;                            //    no volume name;  use vRefNum
  1004.     csparamRec.ioVRefNum = shVRefNum;                        //    volume to search
  1005.     csparamRec.ioMatchPtr = &fsspecHitsArray[0];                //    points to FSSpec array
  1006.     csparamRec.ioReqMatchCount = (long)kshSweepMatches;        //    max no. of matches per sweep
  1007.     csparamRec.ioSearchInfo1 = &cipbrRec_1;                    //    points to first criteria set
  1008.     csparamRec.ioSearchInfo2 = &cipbrRec_2;                    //    points to second criteria set
  1009.     csparamRec.ioSearchTime = 500L;                        //    time out on v.long searches
  1010.  
  1011.     csparamRec.ioOptBuffer = ptrSearchBuff;                    //    point to search cache
  1012.     csparamRec.ioOptBufSize = klgBuffSize;                    //    size of search cache
  1013.  
  1014.      csparamRec.ioActMatchCount = 0L;                        //    init. number of files found
  1015.     csparamRec.ioCatPosition.initialize = 0L;                //    search from beginning of catalogue
  1016.     csparamRec.ioSearchBits = 0L;                            //    init. search criteria
  1017.  
  1018.     //    SEARCH CRITERIA
  1019.     //
  1020.     csparamRec.ioSearchBits += fsSBFlAttrib;                //    search for file/folder
  1021.     csparamRec.ioSearchBits += ( boolExactName ) ? 
  1022.                         fsSBFullName : fsSBPartialName;    //    search using EXACT : PARTIAL name
  1023.         
  1024.     cipbrRec_1.hFileInfo.ioNamePtr = str63FindName;            //    name to search for
  1025.     cipbrRec_2.hFileInfo.ioNamePtr = nil;                    //    set to nil
  1026.     cipbrRec_1.hFileInfo.ioFlAttrib = ( boolForFile ) ? 0L : ioDirMask;    //    set bit 4 for FILES : DIRs
  1027.     cipbrRec_2.hFileInfo.ioFlAttrib = ioDirMask;                //    set mask for bit 4
  1028.  
  1029.     //    search using FILE TYPE
  1030.     //
  1031.     if ( boolForFile && ostypeFileCreator && ostypeFileType )
  1032.         {
  1033.         //
  1034.         //    Reset 'ioSearchBits' to search for name &
  1035.         //    finder info only (finder type/creator is only
  1036.         //    for files anyway!)
  1037.         //
  1038.         //
  1039.         csparamRec.ioSearchBits = fsSBFlFndrInfo;
  1040. //                                                        search using EXACT : PARTIAL name
  1041.         csparamRec.ioSearchBits += ( boolExactName ) ? 
  1042.                             fsSBFullName : fsSBPartialName;
  1043. //                                                        criteria & masks for type searching        
  1044.         cipbrRec_1.hFileInfo.ioFlFndrInfo.fdCreator = ostypeFileCreator;
  1045.         cipbrRec_2.hFileInfo.ioFlFndrInfo.fdCreator = 0xFFFFFFFF;
  1046.         cipbrRec_1.hFileInfo.ioFlFndrInfo.fdType = ostypeFileType;
  1047.         cipbrRec_2.hFileInfo.ioFlFndrInfo.fdType = 0xFFFFFFFF;
  1048.  
  1049.         cipbrRec_1.hFileInfo.ioFlFndrInfo.fdFlags = 0;
  1050.         cipbrRec_2.hFileInfo.ioFlFndrInfo.fdFlags = 0x4000;
  1051.         *(long *)&cipbrRec_1.hFileInfo.ioFlFndrInfo.fdLocation = 0L;
  1052.         *(long *)&cipbrRec_2.hFileInfo.ioFlFndrInfo.fdLocation = 0L;
  1053.         cipbrRec_1.hFileInfo.ioFlFndrInfo.fdFldr = 0;
  1054.         cipbrRec_2.hFileInfo.ioFlFndrInfo.fdFldr = 0;
  1055.         }
  1056.  
  1057.     //    DO SEARCH
  1058.     //
  1059.     do {
  1060.         myErr = PBCatSearch( &csparamRec, FALSE );
  1061. //                                                        eofErr returned when all boolDone
  1062.         boolDone = ( myErr == eofErr );
  1063.  
  1064.         if ( ( ( myErr == noErr ) || boolDone ) && csparamRec.ioActMatchCount > 0 )
  1065.             {
  1066. //                                                        reset count flag
  1067.             boolTooMany = FALSE;
  1068.             for ( i = 0; i < csparamRec.ioActMatchCount && ! boolTooMany; i++ )
  1069.                 {                
  1070. //                                                        copy FSSpec(s) to user's array
  1071.                 BlockMoveData( &csparamRec.ioMatchPtr[i],    
  1072.                             ptrfsspecArray, 
  1073.                             sizeof( FSSpec ) );
  1074. //                                                        make sure always < max
  1075.                 if ( ++shNumFound < *ptrshHits )
  1076.                     ptrfsspecArray++;
  1077. //                                                        break out
  1078.                 else
  1079.                     boolDone = boolTooMany = TRUE;
  1080.                 }
  1081.             }
  1082.         } while ( myErr == noErr && !boolDone );
  1083. //                                                        store number found
  1084.     *ptrshHits = shNumFound;
  1085. //                                                        tidy up memory
  1086.     DisposPtr( ptrSearchBuff );
  1087. //                                                        set OSErr return val
  1088.     if ( shNumFound > 0 && boolDone )
  1089.         myErr = noErr;
  1090. //                                                        file or folder not found
  1091.     else
  1092.         myErr = boolForFile ? fnfErr : dirNFErr;
  1093.         
  1094.     return( myErr );
  1095. }
  1096.  
  1097.  
  1098.  
  1099. OSErr    addAppToDesk( FSSpecPtr ptrfsspecApp, OSType ostypeSig )
  1100. //
  1101. //    Add the application to the desktop
  1102. //    so next search of desktop will find it!
  1103. //
  1104. {
  1105.     DTPBRec        dtpRec;
  1106.     Str255        str255Dummy = { "\p" };
  1107.     OSErr        myErr;
  1108.  
  1109. //                                                        idiot filters
  1110.     ASSERT( ptrfsspecApp != NULL );
  1111.     ASSERT( ostypeSig > 0L );
  1112.  
  1113. //                                                        grab vol's desktop ref
  1114.     dtpRec.ioNamePtr = str255Dummy;
  1115.     dtpRec.ioVRefNum = ptrfsspecApp->vRefNum;
  1116.  
  1117.     myErr = ::PBDTGetPath( &dtpRec );
  1118. //                                                        now insert app into desktop
  1119.     if ( myErr == noErr ) 
  1120.         {
  1121.         dtpRec.ioNamePtr = ptrfsspecApp->name;
  1122.         dtpRec.ioDirID = ptrfsspecApp->parID;
  1123.         dtpRec.ioFileCreator = ostypeSig;
  1124.         
  1125.         myErr = ::PBDTAddAPPLSync( &dtpRec );
  1126.         }
  1127.  
  1128.     return( myErr );
  1129. }
  1130.